package com.github.m6d21.sso.common.util;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import org.apache.commons.collections.CollectionUtils;

/**
 *<p>
 *注解相关工具类（不支持代理的对象）
 *</p>
 *
 * @author liuyang
 * @date 2019/1/3 Thu 15:28:00
 * @since 1.0.0
 */
public class AnnotationHelper {

    private static Map<Class, Map<Class, AnnotationMata>> cache = Maps.newConcurrentMap();

    public static AnnotationHelper newInstance() {
        return new AnnotationHelper();
    }

    public static void clear() {
        cache.clear();
    }

    /**
     *找到类上面的注解（字段）
     *
     * @author liuyang
     * @date 2019-01-03 Thu 17:26:42
     * @param objectType 对象的类型
     * @param annotationClass 注解类型
     */
    public List<Entry<Annotation, Field>> findAnnotationsOnField(Class objectType, Class annotationClass) {
        List<Entry<Annotation, AccessibleObject>> fieldAndMethodList = find(objectType, annotationClass);
        if (CollectionUtils.isEmpty(fieldAndMethodList)) {
            return Lists.newArrayListWithCapacity(0);
        }

        List<Entry<Annotation, Field>> annotationOnFieldList = Lists
            .newArrayListWithCapacity(fieldAndMethodList.size());
        fieldAndMethodList.forEach(entry -> {
            if (entry.getValue() instanceof Field) {
                annotationOnFieldList.add(new AnnotationEntry<>(entry.getKey(), (Field) entry.getValue()));
            }
        });

        return annotationOnFieldList;
    }

    /**
     *找到类上面的注解（方法上）
     *
     * @author liuyang
     * @date 2019-01-03 Thu 17:26:42
     * @param objectType 对象的类型
     * @param annotationClass 注解类型
     */
    public List<Entry<Annotation, Field>> findAnnotationsOnMethod(Class objectType, Class annotationClass) {
        List<Entry<Annotation, AccessibleObject>> fieldAndMethodList = find(objectType, annotationClass);
        if (CollectionUtils.isEmpty(fieldAndMethodList)) {
            return Lists.newArrayListWithCapacity(0);
        }

        List<Entry<Annotation, Field>> annotationOnMethodList = Lists
            .newArrayListWithCapacity(fieldAndMethodList.size());
        fieldAndMethodList.forEach(entry -> {
            if (entry.getValue() instanceof Field) {
                annotationOnMethodList.add(new AnnotationEntry<>(entry.getKey(), (Field) entry.getValue()));
            }
        });

        return annotationOnMethodList;
    }

    /**
     *找到类上面的注解
     *
     * @author liuyang
     * @date 2019-01-03 Thu 17:26:42
     * @param objectType 对象的类型
     * @param annotationClass 注解类型
     */
    public List<Entry<Annotation, AccessibleObject>> find(Class objectType, Class annotationClass) {
        Objects.requireNonNull(objectType, "objectType不能为null");
        Objects.requireNonNull(annotationClass, "annotationType不能为null");

        Map<Class, AnnotationMata> mapping = cache.computeIfAbsent(objectType, k -> Maps.newHashMap());

        AnnotationMata mata = mapping.get(annotationClass);
        if (mata == null) {
            mata = new AnnotationMata(annotationClass, Lists.newArrayList());
            Field[] fields = objectType.getDeclaredFields();
            if (fields != null && fields.length > 0) {
                for (Field field : fields) {
                    Annotation annotation = field.getAnnotation(annotationClass);
                    if (annotation != null) {
                        mata.getAnnotationAccessibleObjectEntryList()
                            .add(new AnnotationEntry<>(annotation, field));
                    }
                }
            }

            Method[] methods = objectType.getDeclaredMethods();
            if (methods != null && methods.length > 0) {
                for (Method method : methods) {
                    Annotation annotation = method.getAnnotation(annotationClass);
                    if (annotation != null) {
                        mata.getAnnotationAccessibleObjectEntryList()
                            .add(new AnnotationEntry<>(annotation, method));
                    }
                }
            }
            mapping.put(annotationClass, mata);
        }

        return mata.getAnnotationAccessibleObjectEntryList();
    }


    public class AnnotationMata {

        private Class annotationClass;
        private List<Entry<Annotation, AccessibleObject>> annotationAccessibleObjectEntryList;

        public AnnotationMata() {
        }

        public AnnotationMata(Class annotationType,
            List<Entry<Annotation, AccessibleObject>> annotationAccessibleObjectEntryList) {
            this.annotationClass = annotationType;
            this.annotationAccessibleObjectEntryList = annotationAccessibleObjectEntryList;
        }

        public Class getAnnotationClass() {
            return annotationClass;
        }

        public void setAnnotationClass(Class annotationClass) {
            this.annotationClass = annotationClass;
        }

        public List<Entry<Annotation, AccessibleObject>> getAnnotationAccessibleObjectEntryList() {
            return annotationAccessibleObjectEntryList;
        }

        public void setAnnotationAccessibleObjectEntryList(
            List<Entry<Annotation, AccessibleObject>> annotationAccessibleObjectEntryList) {
            this.annotationAccessibleObjectEntryList = annotationAccessibleObjectEntryList;
        }
    }

    public class AnnotationEntry<K, V> implements Entry<K, V> {

        private K key;
        private V value;

        public AnnotationEntry(K key, V value) {
            this.key = key;
            this.value = value;
        }

        @Override
        public K getKey() {
            return key;
        }

        @Override
        public V getValue() {
            return value;
        }

        @Override
        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }
    }
}
